//go:build windows
// +build windows

package sub

import (
	"archive/zip"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/urfave/cli/v2"
	"golang.org/x/sys/windows/svc"
	"golang.org/x/sys/windows/svc/mgr"
	"io"
	"lib/structs/ams"
	"lib/utils"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

func SingUpdate() *cli.Command {
	var comm = &cli.Command{
		Name:      "sing",
		Usage:     "更新单个程序",
		UsageText: fmt.Sprintf("eg: todo"),
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:  "fileUri",
				Usage: "文件的下载地址",
			},
			&cli.StringFlag{
				Name:  "target",
				Usage: "压缩文件的释放路径",
			},
			&cli.StringFlag{
				Name:  "zipMd5",
				Usage: "下载文件的MD5",
			},
			&cli.StringFlag{
				Name:  "restartServer",
				Usage: "需要重启的服务名称，名称为空时不需要重启服务",
			},
		},
	}
	comm.Action = func(ctx *cli.Context) error {
		fileUri := ctx.String("fileUri")
		target := ctx.String("target")
		zipMd5 := ctx.String("zipMd5")
		restartServer := ctx.String("restartServer")
		exePath, err := os.Executable()

		fileUri = strings.ReplaceAll(fileUri, "\\", "/")
		df := filepath.Join(filepath.Dir(exePath), fileUri[strings.LastIndex(fileUri, "/")+1:])
		if err = downloadFile(fileUri, df); err != nil {
			_ = os.Remove(df)
			return cli.Exit(fmt.Sprintf("下载文件时发生错误[%s]", err), 1)
		}
		fmd := utils.GetFileMD5(df)
		if strings.ToUpper(fmd) != strings.ToUpper(zipMd5) {
			_ = os.Remove(df)
			return cli.Exit(fmt.Sprintf("下载文件不完整,下载地址：[%s],MD5：[%s],下载文件的MD5:[%s]", fileUri, zipMd5, fmd), 1)
		}
		// 停止服务
		if restartServer != "" {
			err = stopWinService(restartServer)
			if err != nil {
				return cli.Exit(fmt.Sprintf("停止服务：[%s]时发生错误：[%s]", restartServer, err), 1)
			}
		}
		// 解压文件
		if err = unzip(df, target); err != nil {
			_ = os.Remove(df)
			return cli.Exit(fmt.Sprintf("解压文件时发生错误：[%s]", err), 1)
		}
		err = os.Remove(df)
		if err != nil {
			return cli.Exit(fmt.Sprintf("删除zip文件时发生错误：[%s]", err), 1)
		}
		// 启动服务
		if restartServer != "" {
			err = startWinService(restartServer)
			if err != nil {
				return cli.Exit(fmt.Sprintf("启动服务：[%s]时发生错误：[%s]", restartServer, err), 1)
			}
		}
		return nil
	}
	return comm
}

func MultipleUpdate() *cli.Command {
	var comm = &cli.Command{
		Name:      "multiple",
		Usage:     "更新多个程序",
		UsageText: fmt.Sprintf("eg: todo"),
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:  "in",
				Usage: "入参，是一个json字符串，格式参考 ams_client.PluginForUpdate ",
			},
		},
	}
	comm.Action = func(ctx *cli.Context) error {
		in := ctx.String("in")
		var updateArgus ams.PluginForUpdate
		err := json.Unmarshal([]byte(in), &updateArgus)
		if err != nil {
			return cli.Exit(fmt.Sprintf("入参不正确，err:[%s],in:[%s]", err, in), 1)
		}
		// 停止服务
		if updateArgus.RestartServer != "" {
			err = stopWinService(updateArgus.RestartServer)
			if err != nil {
				return cli.Exit(fmt.Sprintf("停止服务：[%s]时发生错误：[%s]", updateArgus.RestartServer, err), 1)
			}
		}
		exePath, err := os.Executable()
		for _, item := range updateArgus.FileList {
			fileUri := item.FileUri
			fileUri = strings.ReplaceAll(fileUri, "\\", "/")
			df := filepath.Join(filepath.Dir(exePath), fileUri[strings.LastIndex(fileUri, "/")+1:])
			if err = downloadFile(fileUri, df); err != nil {
				_ = os.Remove(df)
				return cli.Exit(fmt.Sprintf("下载文件时发生错误[%s]", err), 1)
			}
			fmd := utils.GetFileMD5(df)
			if strings.ToUpper(fmd) != strings.ToUpper(item.ZipMd5) {
				_ = os.Remove(df)
				return cli.Exit(fmt.Sprintf("下载文件不完整,下载地址：[%s],MD5：[%s],下载文件的MD5:[%s]", fileUri, item.ZipMd5, fmd), 1)
			}
			// 解压文件
			if err = unzip(df, item.Target); err != nil {
				_ = os.Remove(df)
				return cli.Exit(fmt.Sprintf("解压文件时发生错误：[%s]", err), 1)
			}
			err = os.Remove(df)
			if err != nil {
				return cli.Exit(fmt.Sprintf("删除zip文件时发生错误：[%s]", err), 1)
			}
		}
		// 启动服务
		if updateArgus.RestartServer != "" {
			err = startWinService(updateArgus.RestartServer)
			if err != nil {
				return cli.Exit(fmt.Sprintf("启动服务：[%s]时发生错误：[%s]", updateArgus.RestartServer, err), 1)
			}
		}
		return nil
	}
	return comm
}

// RestartServer 重启服务
func RestartServer() *cli.Command {
	var comm = &cli.Command{
		Name:      "restartServer",
		Usage:     "重启服务",
		UsageText: fmt.Sprintf("eg: todo"),
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:  "name",
				Usage: "服务名称",
			},
			&cli.StringFlag{
				Name:  "option",
				Usage: "操作方式，可选停止(stop)、启动(start)、重启(restarts)",
			},
		},
	}
	comm.Action = func(ctx *cli.Context) error {
		name := ctx.String("name")
		option := ctx.String("option")
		var err error
		switch option {
		case "stop":
			err = stopWinService(name)
		case "start":
			err = startWinService(name)
		case "restarts":
			err = stopWinService(name)
			if err != nil {
				break
			}
			time.Sleep(1 * time.Second)
			err = startWinService(name)
		default:
			return cli.Exit(fmt.Sprintf("未知操作参数：[%s]", option), 1)
		}
		if err != nil {
			return cli.Exit(fmt.Sprintf("操作服务时发生错误：[%v],操作参数[%s]", err, option), 1)
		}
		return nil
	}
	return comm
}

func downloadFile(uri string, savePath string) (err error) {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}
	res, err := client.Get(uri)
	if err != nil {
		return
	}
	defer res.Body.Close()
	f, err := os.Create(savePath)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = io.Copy(f, res.Body)
	return err
}

// unzip 解压zip文件
func unzip(zipFile string, destDir string) (err error) {
	zipReader, err := zip.OpenReader(zipFile)
	if err != nil {
		return err
	}
	defer zipReader.Close()
	for _, f := range zipReader.File {
		fpath := filepath.Join(destDir, f.Name)
		if f.FileInfo().IsDir() {
			if err = os.MkdirAll(fpath, os.ModePerm); err != nil {
				return err
			}
		} else {
			if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
				return err
			}
			inFile, err := f.Open()
			if err != nil {
				return err
			}
			outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
			if err != nil {
				return err
			}
			_, err = io.Copy(outFile, inFile)
			if err != nil {
				return err
			}
			if err := inFile.Close(); err != nil {
				return err
			}
			if err := outFile.Close(); err != nil {
				return err
			}
		}
	}
	return nil
}

// stopWinService 停止服务
func stopWinService(servName string) (err error) {
	var (
		m      *mgr.Mgr
		s      *mgr.Service
		status svc.Status
		t1     int64
		t2     int64
	)
	t1 = time.Now().Unix()
	m, err = mgr.Connect()
	if err != nil {
		return
	}
	defer m.Disconnect()
	s, err = m.OpenService(servName)
	if err != nil {
		return
	}
	status, err = s.Control(svc.Stop)
	if err != nil {
		return
	}
	ticker := time.NewTicker(1 * time.Second)
	for range ticker.C {
		status, err = s.Query()
		if err != nil {
			return err
		}
		if status.State == svc.Stopped {
			return
		}
		t2 = time.Now().Unix()
		if t2-t1 > 5 {
			err = errors.New("服务未在规定时间内关闭")
			return err
		}
	}
	return nil
}

// startWinService 启动服务
func startWinService(servName string) (err error) {
	var (
		m *mgr.Mgr
		s *mgr.Service
	)
	m, err = mgr.Connect()
	if err != nil {
		return
	}
	defer m.Disconnect()
	s, err = m.OpenService(servName)
	if err != nil {
		return
	}
	err = s.Start()
	return err
}
